Skip to content

Template Based Processor#105

Open
ianjosephwilson wants to merge 59 commits intot-strings:mainfrom
ianjosephwilson:ian/transformer_rebuild
Open

Template Based Processor#105
ianjosephwilson wants to merge 59 commits intot-strings:mainfrom
ianjosephwilson:ian/transformer_rebuild

Conversation

@ianjosephwilson
Copy link
Contributor

@ianjosephwilson ianjosephwilson commented Feb 17, 2026

  • Rewrite Processor To Be Template Based
    • Enumerate interpolation cases to simplify logic and establish framework to hone correctness, specially around text types.
  • Move Node Processor
    • Create submodule nodes/
      • Move Node processor into nodes/processor.py
      • Move nodes.py into nodes/nodes.py
    • Sync tests between the node processor and the str processor using parameterized tests.
  • Change Component Signature(s)
def FunctionComponent(children: Template, **kwargs) -> Template:
    return t""
def FactoryComponent(children: Template, **kwargs) -> Callable[[], Template]:
    def ComponentCallable() -> Template:
        return t""
    return ComponentCallable
  • Change Component Children Processing Order
    • Children is a Template so the actual contents are not processed until after the component is invoked.
  • Change Content Interpolation Rules
    • Zero-arg functions no longer have special treatment:
      • to_html(t'<div>{(lambda: "dynamic")}</div>') != '<div>dynamic</div>'
      • A new formatter named callback can be used which then interpolates the return value
        • to_html(t'<div>{(lambda: "dynamic"):callback}</div>') == '<div>dynamic</div>'
    • Boolean values no longer have special treatment:
      • to_html(t'<div>{False}</div>') == "<div>False</div>"
      • None is still ignored: to_html(t'<div>{None}</div>') == "<div></div>"

@AhnafCodes
Copy link

Move Node processor into nodes.py

Current state: nodes.py contains only data types. processor.py contains all resolution logic.

Current separation is clean — nodes.py is "output types" and processor.py is "template resolution." Moving Node processing logic into nodes.py would blur this(eventually if not immediate)
Currently processor.py → nodes.py one-way dependency is cleaner.

@AhnafCodes
Copy link

AhnafCodes commented Feb 17, 2026

Why change API signature, html -> to_html ?

i would not mind html -> H atleast terse
H(t'<div>{count}</div>'), but why?

name collisions? but, then one can always do import html as to_html

@AhnafCodes
Copy link

AhnafCodes commented Feb 17, 2026

to_html(t'<div>{False}</div>') == "<div>False</div>"

this breaks {condition and template} or {condition and some_func() } which pretty common used in templating.

@ianjosephwilson ianjosephwilson marked this pull request as ready for review February 23, 2026 00:30
@ianjosephwilson
Copy link
Contributor Author

@davepeck @pauleveritt Can you guys take another look at this when you get a chance? It is a very large conceptual change RE: the component signature and the default processor.

I walked back the more ambitious pre-compiled template concept and now both processors are using TNode directly during processing. I have both implementations lined up with each other, tdom/processor.py:to_html and tdom/nodes/processor.py:to_node. They are using shared tests.

The interpolation value options are more strict because I was trying to makes the rules more well defined.

@pauleveritt
Copy link
Contributor

Thanks for all the work @ianjosephwilson I'll see if @davepeck has time this week to go through it together. (Dave: Any afternoon/evening my time is fine.)

@davepeck
Copy link
Contributor

davepeck commented Feb 24, 2026

@ianjosephwilson Thank you for this -- I think I have time this week to re-focus energies and read over in more detail. How does this PR relate to the other open and draft PRs we currently have?

(@AhnafCodes -- thank you for hopping in here, and nice to have you on board! I agree that {condition and "foo"} is something we want to support. It's a very common idiom in JSX-land and, when in doubt, I think we should draw our intuitions from there.)

@ianjosephwilson
Copy link
Contributor Author

@ianjosephwilson Thank you for this -- I think I have time this week to re-focus energies and read over in more detail. How does this PR relate to the other open and draft PRs we currently have?

@davepeck At this point it might be easier to just browse the processor.py and processor_test.py files instead of diffs to get a feel for what changed. I think these are the major things:

--- Iterative Processor? Precompilation? TNodes parsed? Internally uses Node? Template Component Signature Reduced Dynamic Intepretation
This PR Yes No Yes No Yes Yes
Transformer POC (closed) Yes Yes Yes No Yes Yes
Main No No Yes Yes No No
  • Iterative Processor
    • Uses a stack/queue with a loop to process the templates and writes the html strings out as it goes. Usually this uses less memory, keeps the internals internal and doesn't have issues with the function stack limit (although that would probably be unlikely).
  • Precompilation
    • Instead of walking the TNode tree every processing call and dynamically dispatching an interpolation "handler" the handlers were mostly pre-determined and stored in a "precompiled" Template. This "flattened" the processing step into simply walking a Template instead of traversing the TNode tree. You foresaw this as being too complicated and I think the SVG issue was really the straw that broke the camels back, at least for now.
  • Internally use Nodes
    • The prior Node processor would create a tree of Nodes and then "stringify" the nodes into a string.
  • Template Component Signature
    • Simplified component interface where children and output are instances of Template
  • Reduced Dynamic Intepretation
    • Component output is more like a "tag" and less like a "primitive value"
    • Tried to reduce the number of checks per interpolation (for performance, api surface area, debugging, verification, etc.)
      • No special handling for booleans
      • No special handling for callables
      • No special handling for Node

@ianjosephwilson
Copy link
Contributor Author

@davepeck Would it be easier if I broke some of the smaller less related pieces out of this and we merged those first?

Copy link
Contributor

@davepeck davepeck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davepeck Would it be easier if I broke some of the smaller less related pieces out of this and we merged those first?

I'm sort of working through it in one fell swoop right now. Thanks for all the guide posts here. I do think there are some separable pieces we could consider teasing apart into separate PRs, but I dunno... that may be more work than it's worth.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants